package org.quincy.mqtt.test.parser;

import java.nio.ByteBuffer;
import java.util.BitSet;
import java.util.Map;

import org.quincy.mqtt.test.TestUtils;
import org.quincy.mqtt.test.comm.Terminal;
import org.quincy.mqtt.test.comm.TerminalChannel;
import org.quincy.mqtt.test.vo.DeviceId;
import org.quincy.rock.comm.AbstractMessageHeadParser;
import org.quincy.rock.comm.CommunicateException;
import org.quincy.rock.comm.util.CommUtils;
import org.quincy.rock.comm.util.NioUtils;
import org.quincy.rock.core.exception.ParseException;
import org.quincy.rock.core.security.CRC16;
import org.quincy.rock.core.util.StringUtil;

/**
 * <b>报文头解析器。</b>
 * <p><b>详细说明：</b></p>
 * <!-- 在此添加详细说明 -->
 * 无。
 * <p><b>修改列表：</b></p>
 * <table width="100%" cellSpacing=1 cellPadding=3 border=1>
 * <tr bgcolor="#CCCCFF"><td>序号</td><td>作者</td><td>修改日期</td><td>修改内容</td></tr>
 * <!-- 在此添加修改列表，参考第一行内容 -->
 * <tr><td>1</td><td>wks</td><td>2018年5月14日 下午1:28:10</td><td>建立类型</td></tr>
 * 
 * </table>
 * @version 1.0
 * @author wks
 * @since 1.0
 */
public abstract class MessageHeadParser extends AbstractMessageHeadParser<ByteBuffer, ByteBuffer> {

	/**
	 * 是否进行CRC校验。
	 */
	private boolean checkCrc;

	/**
	 * 内容拦截器。
	 */
	private ContentInterceptor contentInterceptor = new ContentInterceptor() {

		@Override
		public ByteBuffer interceptUnpack(TerminalChannel channel, boolean encrypt, int funcCode, ByteBuffer content) {
			return content;
		}

		@Override
		public ByteBuffer interceptPack(TerminalChannel channel, boolean encrypt, int funcCode, ByteBuffer content) {
			return content;
		}

		@Override
		public boolean isEncrypt(TerminalChannel channel, int funcCode) {
			return false;
		}

	};

	/**
	 * <b>构造方法。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param defaultContentType
	 */
	public MessageHeadParser(String defaultContentType) {
		super(defaultContentType);
	}

	/**
	 * <b>是否进行CRC校验。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param checkCrc 是否进行CRC校验
	 */
	public void setCheckCrc(boolean checkCrc) {
		this.checkCrc = checkCrc;
	}

	/**
	 * <b>设置内容拦截器。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param contentInterceptor 内容拦截器
	 */
	public void setContentInterceptor(ContentInterceptor contentInterceptor) {
		this.contentInterceptor = contentInterceptor;
	}

	/** 
	 * pack。
	 * @see org.quincy.rock.comm.MessageHeadParser#pack(java.lang.Object, java.util.Map)
	 */
	@Override
	public ByteBuffer pack(ByteBuffer content, Map<String, Object> ctx) {
		String contentType = ensureContentType(ctx.get(CommUtils.COMM_MSG_TYPE_KEY));
		boolean isBinary = CommUtils.MESSAGE_TYPE_BINARY.equals(contentType);
		int funcCode = (Integer) ctx.get(CommUtils.COMM_FUNCTION_CODE_KEY);
		TerminalChannel channel = (TerminalChannel) ctx.get(CommUtils.COMM_CHANNEL_KEY);
		channel.setRequest(funcCode < 20000);
		int msgId = (Integer) ctx.get(CommUtils.COMM_MSG_ID_KEY);
		//是否忽略返回响应
		Boolean ignoreRespone = (Boolean) ctx.get(CommUtils.COMM_MSG_IGNORE_RESPONE_KEY);
		//版本号
		String ver = (String) ctx.get(CommUtils.COMM_MSG_PROTOCOL_VERSION_KEY);
		//时间戳
		Long time = (Long) ctx.get(CommUtils.COMM_MSG_TIMESTAMP_KEY);
		//本条报文是否要加密
		boolean encrypt = contentInterceptor.isEncrypt(channel, funcCode);
		//拦截加密
		content = contentInterceptor.interceptPack(channel, encrypt, funcCode, content);

		//开始组装报文头
		ByteBuffer message = ByteBuffer.allocate(content.remaining() + 100);
		//报文头
		NioUtils.writeByte(message, 'A', false);
		//标识位
		BitSet bitSet = new BitSet(8);
		bitSet.set(0, Boolean.TRUE.equals(ignoreRespone)); //是否忽略响应		
		bitSet.set(1, isBinary); //正文格式
		bitSet.set(5, encrypt); //是否加密
		NioUtils.writeByte(message, bitSet.toByteArray()[0], false);
		//消息id
		NioUtils.writeInt(message, msgId, false);
		//功能码
		NioUtils.writeShort(message, funcCode, false);
		//发送方
		NioUtils.writeByte(message, channel.getLocalType(), false);
		NioUtils.writeChars(message, channel.getLocalCode(), 10, false);
		//版本
		int ver1 = 1;
		int ver2 = 0;
		if (!StringUtil.isBlank(ver)) {
			int index = ver.indexOf('.');
			if (index == -1) {
				ver1 = Integer.parseInt(ver);
			} else {
				ver1 = Integer.parseInt(ver.substring(0, index));
				ver1 = Integer.parseInt(ver.substring(index + 1));
			}
		}
		NioUtils.writeByte(message, ver1 << 4 + ver2, false);
		//时间戳
		NioUtils.writeTimestamp(message, time == null ? System.currentTimeMillis() : time.longValue(), false);
		//分包
		Integer pkg_total = (Integer) ctx.get(CommUtils.COMM_MSG_PKG_TOTAL_KEY);
		NioUtils.writeByte(message, pkg_total == null ? 1 : pkg_total.intValue(), false);
		Integer pkg_index = (Integer) ctx.get(CommUtils.COMM_MSG_PKG_INDEX_KEY);
		NioUtils.writeByte(message, pkg_index == null ? 0 : pkg_index.intValue(), false);
		NioUtils.writeBytes(message, content, false);
		if (checkCrc) {
			byte[] tamper = this.retrieveTamper(channel.remote(), funcCode);
			int crc = calculateCRC(message.array(), 0, message.position(), tamper);
			NioUtils.writeShort(message, crc, false);
		}
		message.flip();
		//返回
		return message;
	}

	/** 
	 * unpack。
	 * @see org.quincy.rock.comm.MessageHeadParser#unpack(java.lang.Object, java.util.Map)
	 */
	@Override
	public ByteBuffer unpack(ByteBuffer message, Map<String, Object> ctx) {
		int crc = 0;
		if (checkCrc) {
			byte[] data = NioUtils.readBytes(message, message.remaining() - 2);
			crc = NioUtils.readUnsignedShort(message);
			message = ByteBuffer.wrap(data);
		}
		if (message.get() != 'A')
			throw new CommunicateException("非法报文!");
		//标志位
		long flag = NioUtils.readUnsignedByte(message);
		//flag=='@'
		if (flag == '@') {
			//遗嘱报文
			return unpackWill(message, ctx);
		}
		BitSet bitSet = BitSet.valueOf(new long[] { flag });
		//是否忽略返回响应		
		ctx.put(CommUtils.COMM_MSG_IGNORE_RESPONE_KEY, bitSet.get(0));
		//正文格式
		ctx.put(CommUtils.COMM_MSG_TYPE_KEY,
				bitSet.get(1) ? CommUtils.MESSAGE_TYPE_BINARY : CommUtils.MESSAGE_TYPE_JSON);
		//是否是加密报文
		boolean encrypt = bitSet.get(5);
		//消息id
		Integer msgId = message.getInt();
		ctx.put(CommUtils.COMM_MSG_ID_KEY, msgId);
		//功能码
		Integer funcCode = NioUtils.readUnsignedShort(message);
		ctx.put(CommUtils.COMM_FUNCTION_CODE_KEY, funcCode);
		//配置通道
		TerminalChannel channel = (TerminalChannel) ctx.get(CommUtils.COMM_CHANNEL_KEY);
		channel.setRequest(funcCode < 20000);
		channel.setRemoteType(NioUtils.readUnsignedByte(message));
		channel.setRemoteCode(NioUtils.readChars(message, 10));
		//版本号
		int ver = NioUtils.readUnsignedByte(message);
		ctx.put(CommUtils.COMM_MSG_PROTOCOL_VERSION_KEY, (ver >> 4) + "." + (ver & 0x0F));
		//时间戳
		Long time = NioUtils.readTimestamp(message);
		ctx.put(CommUtils.COMM_MSG_TIMESTAMP_KEY, time);
		//分包
		int pkg_total = NioUtils.readUnsignedByte(message);
		ctx.put(CommUtils.COMM_MSG_PKG_TOTAL_KEY, pkg_total);
		int pkg_index = NioUtils.readUnsignedByte(message);
		ctx.put(CommUtils.COMM_MSG_PKG_INDEX_KEY, pkg_index);
		if (checkCrc) {
			byte[] tamper = this.retrieveTamper(channel.remote(), funcCode);
			if (crc != calculateCRC(message.array(), 0, message.limit(), tamper)) {
				throw new ParseException("CRC error!");
			}
		}
		//返回正文
		message = contentInterceptor.interceptUnpack(channel, encrypt, funcCode, message);
		return message;
	}

	/**
	 * <b>获得混淆码。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param term 终端
	 * @param funcCode 功能码
	 * @return 混淆码
	 */
	protected abstract byte[] retrieveTamper(Terminal term, int funcCode);

	/**
	 * <b>计算CRC校验码。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param data 数据数组
	 * @param pos 开始位置 
	 * @param len 长度
	 * @param tamper 混淆码
	 * @return CRC校验码
	 */
	private int calculateCRC(byte[] data, int pos, int len, byte[] tamper) {
		CRC16 checker = new CRC16();
		if (tamper != null)
			checker.update(tamper);
		checker.update(data, pos, len);
		return (int) checker.getValue();
	}

	/**
	 * <b>对遗嘱进行解包处理。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param will 遗嘱
	 * @param ctx 报文上下文
	 * @return 遗嘱对象
	 */
	private ByteBuffer unpackWill(ByteBuffer will, Map<String, Object> ctx) {
		//ctx
		ctx.put(CommUtils.COMM_MSG_TYPE_KEY, getDefaultContentType());
		ctx.put(CommUtils.COMM_MSG_ID_KEY, CommUtils.uniqueMessageIdAsInt());
		ctx.put(CommUtils.COMM_FUNCTION_CODE_KEY, 10000);
		ctx.put(CommUtils.COMM_MSG_PROTOCOL_VERSION_KEY, TestUtils.MSG_PROTOCOL_VER);
		ctx.put(CommUtils.COMM_MSG_TIMESTAMP_KEY, System.currentTimeMillis());
		ctx.put(CommUtils.COMM_MSG_PKG_TOTAL_KEY, 1);
		ctx.put(CommUtils.COMM_MSG_PKG_INDEX_KEY, 0);
		//
		TerminalChannel channel = (TerminalChannel) ctx.get(CommUtils.COMM_CHANNEL_KEY);
		DeviceId deviceId = new DeviceId();
		deviceId.setDeviceType(TestUtils.deviceType2Integer(NioUtils.readChars(will, 2)));
		deviceId.setDeviceNo(NioUtils.readChars(will, 10));
		channel.setRemoteType(deviceId.getDeviceType());
		channel.setRemoteCode(deviceId.getDeviceNo());
		channel.setRequest(true);
		will = ByteBuffer.allocate(20);
		will = deviceId.toBinary(will, ctx);
		will.flip();
		//返回余下的内容
		return will;
	}
}
